/**
* @file    BinInput.cpp
* @brief   Binary array data input
* @date    2014/02
* @version
* Copyright (c)
*/

#include <iostream>
#include <fstream>
#include <cstring>
#include <vector>
#include <algorithm>
#include <cfloat>
#include <cmath>
#include <sys/stat.h>

#include "BinInput.h"

const int MAX_STRING_LEN = 256;
const int HF_MAX_STR_LEN = MAX_STRING_LEN / 2;
const int QT_MAX_STR_LEN = MAX_STRING_LEN / 4;

const UGINT64 BinArray_hlen = sizeof(UGINT64) + sizeof(double) + sizeof(char) + MAX_STRING_LEN;

PropArray::PropArray() {
    type = 'd';
    length = 0;
    fdata = nullptr;
    idata = nullptr;

    on_grid = false;

    time = 0.0;
    time_type = 't';

    Max = Min = Avg = 0.0;
};

PropArray::PropArray(const PropArray& src) {
    type = src.type;
    length = src.length;

    if (src.type == 'd' && src.fdata != nullptr) {
        fdata = new double[src.length];
        memcpy(fdata, src.fdata, src.length * sizeof(double));
    } else {
        fdata = nullptr;
    }

    if (src.type == 'i' && src.idata != nullptr) {
        idata = new int[src.length];
        memcpy(idata, src.idata, src.length * sizeof(int));
    } else {
        idata = nullptr;
    }

    on_grid = src.on_grid;

    time = src.time;
    time_type = src.time_type;

    Max = src.Max;
    Min = src.Min;
    Avg = src.Avg;

    name = src.name;
    unit = src.unit;
};

PropArray::~PropArray() {
    delete[] fdata;
    delete[] idata;
};

void PropArray::operator=(const PropArray& src) {
    type = src.type;
    length = src.length;

    delete[] fdata;
    fdata = nullptr;
    if (src.type == 'd' && src.fdata != nullptr) {
        fdata = new double[src.length];
        memcpy(fdata, src.fdata, src.length * sizeof(double));
    }

    delete[] idata;
    idata = nullptr;
    if (src.type == 'i' && src.idata != nullptr) {
        idata = new int[src.length];
        memcpy(idata, src.idata, src.length * sizeof(int));
    }

    on_grid = src.on_grid;

    time = src.time;
    time_type = src.time_type;

    Max = src.Max;
    Min = src.Min;
    Avg = src.Avg;

    name = src.name;
    unit = src.unit;
};

void PropArray::Clear() {
    delete[] fdata;
    delete[] idata;
    fdata = nullptr;
    idata = nullptr;
    type = 'd';
    on_grid = false;
    length = 0;
    time = 0.0;
    time_type = 't';
    Max = Min = Avg = 0.0;

    name.clear();
    unit.clear();
};

bool PropArray::ReMap(const UGINT64 Net_size, const UGINT64 Pre_Net_size, const std::vector<transfer>& Moves) {
    if (length != Pre_Net_size) {
        return false;
    }

    double* a = new double[Net_size];
    for (int i = 0; i < Net_size; ++i) {
        a[i] = 0.0;
    }

    if (type == 'd') {
        for (const transfer& move : Moves) {
            a[move.to] += fdata[move.from % length] * move.mult; // %防止下标越界
        }
    } else {
        for (const transfer& move : Moves) {
            a[move.to] += static_cast<double>(idata[move.from % length]) * move.mult; // %防止下标越界
        }
    }

    // 更新最大最小值
    Max = -DBL_MAX, Min = DBL_MAX, Avg = 0.0;
    for (int i = 0; i < Net_size; ++i) {
        Max = std::max(a[i], Max);
        Min = std::min(a[i], Min);
        Avg += a[i];
    }
    Avg /= static_cast<double>(std::max(Net_size, static_cast<UGINT64>(1)));

    length = Net_size;

    if (type == 'd') {
        delete[] fdata;
        fdata = a;
        a = nullptr;
    } else {
        delete[] idata;
        idata = new int[Net_size];

        for (int i = 0; i < Net_size; ++i) {
            idata[i] = static_cast<int>(std::round(a[i]));
        }

        delete[] a;
        a = nullptr;
    }

    return true;
};

static void ScanBinArray(std::ifstream& fp, Name_Loc& a) {
    UGINT64 char_len;

    a.offset = fp.tellg();
    // fp.read(reinterpret_cast<char*>(&char_len), sizeof(UGINT64));
    // fp.read(reinterpret_cast<char*>(&a.time), sizeof(double));
    // fp.read(reinterpret_cast<char*>(&a.type), sizeof(char));
    fp.read((char*)&char_len, sizeof(UGINT64));
    fp.read((char*)&a.time, sizeof(double));
    fp.read((char*)&a.type, sizeof(char)); 

    char pname[HF_MAX_STR_LEN], punit[HF_MAX_STR_LEN];
    fp.read(pname, HF_MAX_STR_LEN);
    fp.read(punit, HF_MAX_STR_LEN);

    a.name = pname;
    a.unit = punit;
    memcpy(&a.Max, punit + QT_MAX_STR_LEN, sizeof(double));
    memcpy(&a.Min, punit + QT_MAX_STR_LEN + sizeof(double), sizeof(double));
    memcpy(&a.Avg, punit + QT_MAX_STR_LEN + 2 * sizeof(double), sizeof(double));

    a.time_type = punit[QT_MAX_STR_LEN + 3 * sizeof(double)];
    a.on_grid = (punit[QT_MAX_STR_LEN + 3 * sizeof(double) + 1] == 'y');

    UGINT64 net_len = (char_len - sizeof(double) - sizeof(char) - MAX_STRING_LEN);
    fp.seekg(net_len, std::ios::cur);

    if (a.type == 'd') {
        a.length = net_len / sizeof(double);
    } else if (a.type == 'i') {
        a.length = net_len / sizeof(int);
    }
};

BinReader::BinReader(const std::string& _casename) {
    Scan(_casename);
};

void BinReader::Scan(const std::string& _casename) {
    Clear();

    casename = _casename;
    double max_time = -DBL_MAX;

    // read dynamic status
    for (UGINT64 fcount = 0;; ++fcount) {
        std::ifstream fp;

        std::string filename = casename + "_stu" + (fcount == 0 ? "" : std::to_string(fcount)) + ".bin";
        fp.open(filename, std::ios::binary);
        if (fp.fail()) {
            if (fcount == 0) {
                std::cout << " File \"" << filename << "\" does not exist! " << std::endl;
            }
            break;
        }

        int cur_step = -1;
        double cur_time = -DBL_MAX;

        while (fp.peek() != EOF) {
            Name_Loc tmp_prop;
            tmp_prop.fcount = fcount;
            ScanBinArray(fp, tmp_prop);

            if (tmp_prop.time != cur_time || dym_props.empty()) {
                int step = dym_props.size();
                if (tmp_prop.time > max_time) {
                    step = dym_props.size();
                } else {
                    for (step = 0; step < dym_props.size(); ++step) {
                        if (!dym_props[step].empty() && dym_props[step][0].time == tmp_prop.time) break;
                    }
                }

                if (step < dym_props.size()) {
                    cur_step = step;
                    dym_props[cur_step].push_back(tmp_prop);
                } else {
                    dym_props.push_back({tmp_prop});
                    cur_step = dym_props.size() - 1;
                    max_time = tmp_prop.time;
                }

                cur_time = tmp_prop.time;
            } else if (cur_step < dym_props.size()) {
                dym_props[cur_step].push_back(tmp_prop);
            }
        }

        fp.close();
    }

    // read static property data
    std::string filename = casename + "_geom.bin";
    std::ifstream stat_prop_fp(filename, std::ios::binary);
    if (!stat_prop_fp.fail()) {
        Name_Loc tmp_prop;
        while (stat_prop_fp.peek() != EOF) {
            ScanBinArray(stat_prop_fp, tmp_prop);

            if (tmp_prop.name == "G2NMAP") {
                UGINT64 gross_size = tmp_prop.length;
                int* tmp_data = new int[gross_size];
                stat_prop_fp.seekg(tmp_prop.offset + sizeof(UGINT64) + sizeof(double) + sizeof(char) + MAX_STRING_LEN, std::ios::beg);
                stat_prop_fp.read(reinterpret_cast<char*>(tmp_data), gross_size * sizeof(int));

                Gross2Net.assign(tmp_data, tmp_data + gross_size);
                delete[] tmp_data;
            }
            if (tmp_prop.name == "N2GMAP") {
                UGINT64 net_size = tmp_prop.length;
                int* tmp_data = new int[net_size];
                stat_prop_fp.seekg(tmp_prop.offset + sizeof(UGINT64) + sizeof(double) + sizeof(char) + MAX_STRING_LEN, std::ios::beg);
                stat_prop_fp.read(reinterpret_cast<char*>(tmp_data), net_size * sizeof(int));

                Net2Gross.assign(tmp_data, tmp_data + net_size);
                delete[] tmp_data;
            }
            stat_props.push_back(tmp_prop);
        }
        stat_prop_fp.close();
    }
    PropArray GRIDINFO_array;
    GetStaticData("GRIDINFO", GRIDINFO_array);
    if (GRIDINFO_array.length == 32) {
        for (UGINT64 i = 0; i < 20; ++i) {
            GridInfo[i] = (UGINT64)GRIDINFO_array[i];
        }
        AxisOffset[0] = GRIDINFO_array[20];
        AxisOffset[1] = GRIDINFO_array[21];
        AxisOffset[2] = GRIDINFO_array[22];
    }
    else {
        std::cout << " Error: length of GRIDINFO is not 32" << std::endl;
    }

    if (GridInfo[2] != Gross2Net.size()) {
        std::cout << " Error: G2NMAP length does not equal to GRIDINFO[2]" << std::endl;
    }

    if (GridInfo[8] != Net2Gross.size()) {
        std::cout << " Error: NTGMAP length does not equal to GRIDINFO[8]" << std::endl;
    }

    origin_net_size = Net2Gross.size();
    for (int i = 0; i < 32; ++i) {
        origin_GridInfo[i] = GridInfo[i];
    }
    origin_Gross2Net = Gross2Net;
};

void BinReader::Clear() {
    // 清除 gross to net 和 net to gross 的网格映射
    Gross2Net.clear();
    Net2Gross.clear();

    dym_props.clear();
    stat_props.clear();

    origin_net_size = 0;
    origin_Gross2Net.clear();
    memset(origin_GridInfo, 0x0, sizeof(GridInfo));
    Moves.clear();

    casename.clear();

    memset(GridInfo, 0x0, sizeof(GridInfo));
    memset(AxisOffset, 0x0, sizeof(AxisOffset));
}

bool BinReader::GetDynamicData(const UGINT64 tstep, const std::string& name, PropArray& a, const bool remap) const {
    bool is_get = false;
    if (tstep <= dym_props.size()) {
        const std::vector<Name_Loc>& props = dym_props[tstep - 1];
        auto itr = std::find_if(props.begin(), props.end(), [&name](const Name_Loc& p) { return p.name == name; });

        if (itr != props.end()) {
            std::string filename = casename + "_stu" + (itr->fcount > 0 ? std::to_string(itr->fcount) : "") + ".bin";
            std::ifstream dym_prop_fp(filename, std::ios::binary);

            if (!dym_prop_fp.fail()) {
                a.Clear();

                struct stat finfo;
                int fail = stat(filename.c_str(), &finfo);

                if (fail == 0 && itr->offset <= static_cast<UGINT64>(finfo.st_size) - BinArray_hlen) {
                    UGINT64 char_len;
                    dym_prop_fp.seekg(itr->offset, std::ios::beg);
                    dym_prop_fp.read(reinterpret_cast<char*>(&char_len), sizeof(UGINT64));
                    dym_prop_fp.read(reinterpret_cast<char*>(&a.time), sizeof(double));
                    dym_prop_fp.read(reinterpret_cast<char*>(&a.type), sizeof(char));

                    char pname[HF_MAX_STR_LEN], punit[HF_MAX_STR_LEN];
                    dym_prop_fp.read(pname, HF_MAX_STR_LEN);
                    dym_prop_fp.read(punit, HF_MAX_STR_LEN);

                    a.name = pname;
                    a.unit = punit;
                    memcpy(&a.Max, punit + QT_MAX_STR_LEN, sizeof(double));
                    memcpy(&a.Min, punit + QT_MAX_STR_LEN + sizeof(double), sizeof(double));
                    memcpy(&a.Avg, punit + QT_MAX_STR_LEN + 2 * sizeof(double), sizeof(double));

                    a.time_type = punit[QT_MAX_STR_LEN + 3 * sizeof(double)];
                    a.on_grid = (punit[QT_MAX_STR_LEN + 3 * sizeof(double) + 1] == 'y');

                    UGINT64 net_len = (char_len - sizeof(double) - sizeof(char) - MAX_STRING_LEN);
                    if (a.type == 'd') {
                        a.length = net_len / sizeof(double);
                        a.fdata = new double[a.length];
                        dym_prop_fp.read(reinterpret_cast<char*>(a.fdata), net_len);
                    } else if (a.type == 'i') {
                        a.length = net_len / sizeof(int);
                        a.idata = new int[a.length];
                        dym_prop_fp.read(reinterpret_cast<char*>(a.idata), net_len);
                    }

                    if (!Moves.empty() && (a.on_grid || remap)) {
                        a.ReMap(NetSize(), origin_net_size, Moves);
                    }

                    is_get = true;
                }

                dym_prop_fp.close();
            }
        }
    }

    return is_get;
}

bool BinReader::GetStaticData(const std::string& name, PropArray& a, const bool remap) const {
    auto itr = std::find_if(stat_props.begin(), stat_props.end(), [&name](const Name_Loc& p) { return p.name == name; });

    if (itr != stat_props.end()) {
        std::string filename = casename + "_geom.bin";
        std::ifstream stat_prop_fp(filename, std::ios::binary);

        if (!stat_prop_fp.fail()) {
            UGINT64 char_len;
            stat_prop_fp.seekg(itr->offset, std::ios::beg);
            stat_prop_fp.read(reinterpret_cast<char*>(&char_len), sizeof(UGINT64));
            stat_prop_fp.read(reinterpret_cast<char*>(&a.time), sizeof(double));
            stat_prop_fp.read(reinterpret_cast<char*>(&a.type), sizeof(char));

            char pname[HF_MAX_STR_LEN], punit[HF_MAX_STR_LEN];
            stat_prop_fp.read(pname, HF_MAX_STR_LEN);
            stat_prop_fp.read(punit, HF_MAX_STR_LEN);

            a.name = pname;
            a.unit = punit;
            memcpy(&a.Max, punit + QT_MAX_STR_LEN, sizeof(double));
            memcpy(&a.Min, punit + QT_MAX_STR_LEN + sizeof(double), sizeof(double));
            memcpy(&a.Avg, punit + QT_MAX_STR_LEN + 2 * sizeof(double), sizeof(double));

            a.time_type = punit[QT_MAX_STR_LEN + 3 * sizeof(double)];
            a.on_grid = (punit[QT_MAX_STR_LEN + 3 * sizeof(double) + 1] == 'y');

            UGINT64 net_len = (char_len - sizeof(double) - sizeof(char) - MAX_STRING_LEN);
            if (a.type == 'd') {
                a.length = net_len / sizeof(double);
                a.fdata = new double[a.length];
                stat_prop_fp.read(reinterpret_cast<char*>(a.fdata), net_len);
            } else if (a.type == 'i') {
                a.length = net_len / sizeof(int);
                a.idata = new int[a.length];
                stat_prop_fp.read(reinterpret_cast<char*>(a.idata), net_len);
            }

            stat_prop_fp.close();

            if (!Moves.empty() && (a.on_grid || remap)) {
                a.ReMap(NetSize(), origin_net_size, Moves);
            }

            return true;
        }
    }

    return false;
}
